﻿using System;
using System.Collections.Generic;
using System.ServiceModel;
using System.Threading.Tasks;
using Wombat.Core;
using Wombat.Core.Diagnostics;
using Wombat.Core.Factories;
using Wombat.Core.Services;

namespace Wombat.WcfClientGate
{
    public sealed class WcfExportGate : IGate, IConfigurable, IInitializable, ILongLifeGate, IDiagnosticsObservable, IStartable
    {
        public delegate WcfExportGate Factory();

        public static class ConfigurationKeys
        {
            public static readonly string ServiceEndpoint = "ServiceEndPoint";
            public static readonly string Guid = "Guid";
        }
        private readonly IConfigurationService _configurationService;
        private readonly ITagRpository _tagRpository;
        private readonly IDiagnosticsService _diagnosticsService;
        private ITagDataBroadcasterService _tagDataBroadcasterService;

        public WcfExportGate(IConfigurationService configurationService, ITagRpository tagRpository, IDiagnosticsService diagnosticsService)
        {
            _configurationService = configurationService;
            _tagRpository = tagRpository;
            _diagnosticsService = diagnosticsService;
            _observerPort = (IObserverPort<ITagValue>)PortFactory.Create<ITagValue>("ObserverPort", PortBehavior.Input,
                                              OnNext,
                                              OnError,
                                              OnCompleted);
        }

        public Guid Guid { get; private set; }

        private readonly IObserverPort<ITagValue> _observerPort;
        private ChannelFactory<ITagDataBroadcasterService> _channelFactory;
        private string _serviceEndPoint;

        public IEnumerable<IPort> Ports { get; private set; }

        public IDictionary<string, IConfigurationItem> Configurations { get; private set; }

        void IInitializable.Initialize()
        {
            Configurations = _configurationService.ReadConfigurations(typeof(WcfExportGate));
            Ports = new[] { _observerPort };
            Guid = Guid.Parse((string)Configurations[ConfigurationKeys.Guid].Value);
            _serviceEndPoint = Configurations[ConfigurationKeys.ServiceEndpoint].Value as string;
        }

        private async Task StartProxy(string endPointAddress)
        {
            var binding = new BasicHttpBinding();
            var endpoint = new EndpointAddress(endPointAddress);
            _channelFactory = new ChannelFactory<ITagDataBroadcasterService>(binding, endpoint);
            try
            {
                _tagDataBroadcasterService = await Task.Factory.StartNew(() => _channelFactory.CreateChannel());
                if (_channelFactory.State == CommunicationState.Faulted)
                {
                    _channelFactory.Abort();
                    await StopProxyAsync();
                }
                else
                    if (_channelFactory.State != CommunicationState.Opened)
                    await Task.Factory.FromAsync((callback, o) => _channelFactory.BeginOpen(callback, o),
                                                 r => _channelFactory.EndOpen(r), null);
            }
            catch (Exception exception)
            {
                _diagnosticsService.WriteFault("Error openning proxy to service '{0}': {1}", endPointAddress, exception);
                if (_channelFactory.State == CommunicationState.Faulted)
                    _channelFactory.Abort();
                throw;
            }

        }

        private async void OnNext(ITagValue tagValue)
        {
            var tag = _tagRpository.Get(tagValue.TagId);
            if (tag.DataType == typeof (double?))
            {
                await Task.Factory.StartNew(() => SafeCall(() => _tagDataBroadcasterService.PublishDouble(tagValue.TagId, tagValue.Time, (double?)tagValue.ObjectValue)));
            }
        }

        private void SafeCall(Action action)
        {
            try
            {
                if (_channelFactory.State == CommunicationState.Opened)
                    action();
                else if (_channelFactory.State != CommunicationState.Opening)
                {
                    RefreshChannel();
                }
            }
            catch (Exception exception)
            {
                _diagnosticsService.WriteFault("Could not publish to gate.", exception);
                RefreshChannel();
            }
        }

        private void RefreshChannel()
        {
            _channelFactory.Close();
            _tagDataBroadcasterService = _channelFactory.CreateChannel();
        }

        private void OnError(Exception exception)
        {
            //Do nothing
        }

        private void OnCompleted()
        {
            Dispose();
        }

        public void Dispose()
        {
            StopProxyAsync().Wait();
        }

        private async Task StopProxyAsync()
        {
            // ReSharper disable SuspiciousTypeConversion.Global
            var clientChannel = (IClientChannel) _tagDataBroadcasterService;
            // ReSharper restore SuspiciousTypeConversion.Global
            await
                Task.Factory.FromAsync(clientChannel.BeginClose,
                                       clientChannel.EndClose, null);

            await Task.Factory.FromAsync((callBack, o) => _channelFactory.BeginClose(callBack, o),
                                   r => _channelFactory.EndClose(r), null);
        }

        void IDiagnosticsObservable.SubscribeDiagnostics(DiagnosticsLevel level, IObserver<IDiagnosticsMessage> errorObserver)
        {
            throw new NotImplementedException();
        }

        public async Task StartAsync()
        {
            await StartProxy(_serviceEndPoint);
        }

        public async Task StopAsync()
        {
            await StopProxyAsync();
        }
    }
}
